#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define mes_len 256 //固定长度

int str_ex(char in_str[])
{
    char ex_str[mes_len] = "";//创建临时字符数组
    strcpy(ex_str,in_str);//cp传入的字符数组
    char *str1 = strtok(ex_str,".");//通过strtok获取每个部分的内容
    char *str2 = strtok(NULL,".");
    char *str3 = strtok(NULL,".");
    char *str4 = strtok(NULL,",");
    char *str5 = strtok(NULL,",");

    char ex_num[mes_len] = "";//创建一个字符数组,用来存储前面被分格出来的全部字符
    //因为strtok分格字符串时,如果全部的内容已经被前面的strtok分格完,那么当前的char*变量就等同于未初始化的指针变量,地址为NULL,指向的内容自然也为NULL
    if (str1!=NULL && str2!=NULL && str3!=NULL && str4!=NULL && str5!=NULL)//这里判断strtok分格出来的的字符串是否为NULL
    {
        sprintf(ex_num,"%s%s%s%s%s",str1,str2,str3,str4,str5);//拼接全部分格出来的字符串,以供后面检查
    }
    else//如果有字符串为NULL,那么打印提示,并直接返回
    {
        printf("请按正确格式输入内容!格式:IP,端口,内容!\n");
        return -1;
    }
    for (int i = 0; i < strlen(ex_num); i++)
    {   //通过循环和下标,对前面拼接的字符串进行逐个检查,如果不为数字,就打印提示并返回
        if (ex_num[i]<'0'||ex_num[i]>'9')
        {
            printf("请按正确格式输入内容!格式:IP,端口,内容!\n");
            return -1;
        }
    }
    //判断ip分格出来的字符串是否超过255,因为ip的每个部分都是一个字节,最高只能到255
    if (atoi(str1)>255||atoi(str2)>255||atoi(str3)>255||atoi(str4)>255)
    {
        printf("请按正确格式输入内容!格式:IP,端口,内容!\n");
        return -1;
    }
    
    unsigned short str5_num = 0;//通过sscanf转换端口的字符串为整形
    sscanf(str5,"%hu",&str5_num);
    //判断端口号的范围,不在规定范围内就打印提示并返回
    if (str5_num>65535||str5_num<=0)
    {   
        printf("端口号范围在1~65535!请重新输入!\n");
        return -1;
    }

    return 0;
}

void *send_mes(void* agv)//发送消息函数
{
    int *fd = (int*)agv;//通过强转获取外部传入的描述符
    while (1)//循环保证线程一直处于运行状态
    {
        char scanf_mes[mes_len] = "";//记录输入的内容
        scanf("%s",scanf_mes);//输入内容

        if (strcmp(scanf_mes,"close")==0)break;//判断输入的是否是关闭指令
        int ex = str_ex(scanf_mes);//判断输入的内容是否符合标准
        if (ex < 0)continue;//如果不符合标准就跳过本次循环
        
        char* ip_str = strtok(scanf_mes,",");//对输入的内容进行解析,此处为ip
        char* port_str = strtok(NULL,",");//端口号
        char* send_mess = strtok(NULL,",");//发送的消息
        unsigned long send_mess_len = 0;//后面sendto发送的长度
        if (send_mess != NULL)send_mess_len = strlen(send_mess);//判断发送的消息是否为空,为空就不使用strlen,因为如果测的字符串为null,就会报错 
        unsigned short port_num = atoi(port_str);//将前面字符串的端口号转成整形

        struct sockaddr_in in_addr;//创建存储目标ip和端口的结构体
        bzero(&in_addr,sizeof(in_addr));//对结构体清零
        in_addr.sin_family = AF_INET;//family存放协议
        in_addr.sin_port = htons(port_num);//存放端口
        inet_pton(AF_INET,ip_str,&in_addr.sin_addr.s_addr);//存放ip
        sendto(*fd,send_mess,send_mess_len,0,(struct sockaddr *)&in_addr,sizeof(in_addr));//将需要发送的内容通过sockfd发到in_addr指定的ip
    }
    return 0;
}

void *recv_mes(void* agv)//接受消息函数
{
    int *fd = (int*)agv;//通过强转获取外部传入的描述符
    while (1)//循环保证线程一直处于运行状态
    {
        char buf[256] = "1";//存储接受到的消息
        struct sockaddr addr_recv;//创建存储接受到的ip和端口号的结构体
        int addr_recv_len = sizeof(addr_recv);//对结构体清零
        int len = recvfrom(*fd,buf,sizeof(buf),0,&addr_recv,&addr_recv_len);//将接收到的内容放到buf里面,ip和端口号放到addr_recv里面
        struct sockaddr_in *addr_in = (struct sockaddr_in*)&addr_recv; //通过强转成addr_in,就可以访问之前adr_recv里面的内容
        char ip_str[INET_ADDRSTRLEN] = "";//存储后面转换成字符串的ip
        inet_ntop(AF_INET,&addr_in->sin_addr.s_addr,ip_str,sizeof(ip_str));//将s_addr里面的内容按每个字节分开组成字符串放到ip_str里面
        printf("%s(%d):%s(%d)\n",ip_str,ntohs(addr_in->sin_port),buf,len);//打印接受到的消息
    }
    return 0;
}

int argv_ex(int argc_in,char const *argv_in)
{
    //此函数用来判断输入参数的个数及类型,所以需要一个参数,如果缺少参数或者多输入参数,则会打印提示并退出
    if ((argc_in != 2))
    {
        printf("输入格式不对!格式: a.out 端口\n");
        return -1;
    }
    for (int i = 0; i < strlen(argv_in); i++)
    {   //通过循环和下标,对输入的参数进行逐个检查,如果不为数字,就打印提示并返回
        if (argv_in[1]<'0'||argv_in[1]>'9')
        {
            printf("参数必须是数值!\n");
            return -1;
        }
    }
    //判断输入的参数是否在范围内,否则打印提示并退出
    if ((atoi(argv_in)>65535) || (atoi(argv_in)<1))
    {
        printf("输入参数不对!参数为端口,数值范围为1-65535!\n");
        return -1;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    int ex_argv_num = argv_ex(argc,argv[1]);
    if (ex_argv_num < 0)return 0;
    
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建套接字描述符
    if (sockfd < 0)//判断是否创建成功,如果未创建成功就直接结束并打印错误信息
    {
        perror("sockfd");
        return 0;
    }
    int yes = 1;//后面setsockopt的参数
    setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&yes,sizeof(yes));//允许发送广播数据包
    struct sockaddr_in bind_addr;//创建需要绑定的本地ip结构体
    bzero(&bind_addr,sizeof(bind_addr));//初始化本地ip结构体
    bind_addr.sin_family = AF_INET;//协议
    bind_addr.sin_port = htons(atoi(argv[1]));//通过输入的参数绑定本地端口
    bind_addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY 绑定本地机的所有IP地址 
    int bind_T = bind(sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));// 为套接字描述符绑定本地ip和端口
    if (bind_T < 0)//通过bind的返回值判断是否绑定成功,未成功就关闭描述符并且打印错误信息以及直接结束
    {
        perror("bind");
        close(sockfd);
        return 0;
    }
   
    pthread_t thread1;//创建第一个线程号
    pthread_t thread2;//创建第二个线程号

    pthread_create(&thread1,NULL,send_mes,&sockfd);//绑定线程号和函数,并且调用线程,传入参数描述符
    pthread_create(&thread2,NULL,recv_mes,&sockfd);//同上

    pthread_join(thread1,NULL);//主进程等待发送线程的结束
    pthread_cancel(thread2);//发送线程结束直接kill掉接受线程,否则接受线程一直处于阻塞状态

    close(sockfd);//最后关闭描述符
    return 0;
}

